package fr.mywiki.business.versioning.diff;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Object built from a <code>Difference</code> object and <tt>from</tt> and
 * <tt>to</tt> elements, enables the retrieval of the <tt>to</tt> element
 * from <tt>from</tt> and a <code>List</code> of <code>VersionDiffObj</code>
 * objects.
 * 
 * @author tlombard
 */
public class VersionDiff implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
   * A <code>List</code> of <code>VersionDiffObject</code> Objects declaring
   * the <i>best</i> way to find <tt>to</tt> from <tt>from</tt> String.
   */
	private List diffObjs;

	private VersionDiff() {
	}

	public VersionDiff(Diff diff, String to) {
		this.diffObjs = new ArrayList();
		Iterator itDiff = diff.diff().iterator();
		while (itDiff.hasNext()) {
			Difference diffObj = (Difference) itDiff.next();
			this.diffObjs.add(new VersionDiffObject(diffObj, to));
		}
	}

	/**
   * Builds the <code>VersionDiff</code> object from a String being the string
   * representation of it.
   * 
   * @param str
   *          the String representation of the VersionDiff object.
   */
	public VersionDiff(String str) {
		diffObjs = new ArrayList();

		String[] vdos = str.split(";"); // VersionDiffObject's are isolated

		for (int i = 0; i < vdos.length; i++) {
			String ch = vdos[i].trim(); // Something like (a,b,c,'d')
			String vdo = ch.substring(1, ch.length() - 1).trim(); // Something like
                                                            // a,b,c,'d'
			int index = vdo.indexOf(',');
			int ds = Integer.parseInt(vdo.substring(0, index)); // del start
			int index2 = vdo.indexOf(',', index+1);
			int de = Integer.parseInt(vdo.substring(index+1, index2).trim()); // del end
			int index3 = vdo.indexOf(',', index2+1);
			int as = Integer.parseInt(vdo.substring(index2+1, index3).trim()); // add start
			String astrbis = vdo.substring(index3).trim(); // 'd'
			String astr = astrbis.substring(3, astrbis.length() - 1).trim(); // d
			diffObjs.add(new VersionDiffObject(ds, de, as, astr));
		}
	}

	/**
   * Returns a String representation of the <code>diffObjs</code> variable.
   * The form of the object returned is <tt>{diffObj1, diffObj2,...}</tt>
   * where diffObji is the String representation of each VersionDiffObject.
   * 
   * @return a String object.
   */
	public String diffObjToString() {
		StringBuffer buffer = new StringBuffer();

		Iterator itDiffObj = diffObjs.iterator();
		while (itDiffObj.hasNext()) {
			buffer.append(itDiffObj.next().toString());
			if (itDiffObj.hasNext())
				buffer.append("; ");
		}

		return buffer.toString();
	}

	/**
   * Using the <tt>from</tt> text given in parameter, this method can return
   * the <tt>to</tt> text, coded by the <code>VersionDiff</code> object.
   * 
   * @param from
   *          a String object
   * @return a String object
   */
	public String getString(String from) {
		int shift = 0;
		String to = from;
		Iterator itDiffObj = diffObjs.iterator();
		while(itDiffObj.hasNext()) {
			VersionDiffObject vdo = (VersionDiffObject) itDiffObj.next();
			vdo.shift(shift);
			to = vdo.getString(to);
			shift += vdo.getShift();
		}	
		return to;
	}
	
	public String toString() {
		return diffObjToString();
	}
	
	/**
   * An equivalent of the Difference object in which we don't need to know the
   * <tt>to</tt> String.
   */
	class VersionDiffObject {
		int delStart;

		int delEnd;

		int addStart;

		String addString;

		public VersionDiffObject(int ds, int de, int as, String aStr) {
			this.delStart = ds;
			this.delEnd = de;
			this.addStart = as;
			this.addString = aStr;
		}

		public VersionDiffObject(Difference difference, String to) {
			this.delStart = difference.getDeletedStart();
			this.delEnd = difference.getDeletedEnd();
			int as = difference.getAddedStart();
			this.addStart = as;
			try {
				this.addString = to.substring(as, difference.getAddedEnd() + 1);
			} catch (StringIndexOutOfBoundsException e) {
				this.addString = new String();
			}
		}

		/**
     * Returns a String representation of the current object. The form is :
     * <tt>(delStart, delEnd, addStart, 'addString')</tt>
     */
		public String toString() {
			StringBuffer buffer = new StringBuffer();

			buffer.append("(");
			buffer.append(this.delStart + ", " + this.delEnd + ", " + this.addStart
					+ ", '" + this.addString + "'");
			buffer.append(")");

			return buffer.toString();
		}
		
		/**
	   * Using the <tt>from</tt> text given in parameter, this method can return
	   * the <tt>to</tt> text, coded by the <code>VersionDiffObject</code> object.
	   * 
	   * @param from
	   *          a String object
	   * @return a String object
	   */
		public String getString(String from) {
			String to = from;
			
			to = getDeleted(from);
			to = getAdded(to);
			
			return to;
		}
		
		/**
		 * Using the <tt>from</tt> text given in parameter, this method can return
		 * an intermediate String between <tt>from</tt> and <tt>to</tt>, using
		 * only the deletion information stored in the
		 * <code>VersionFiddObject</code>.
		 * 
		 * @param from
		 *          a String object
		 * @return a String object
		 */
		public String getDeleted(String from) {
			String to = from;
			
			if(delEnd<0)
				return to;
			
			to = to.substring(0, delStart) + to.substring(delEnd + 1);
			
			return to;
		}
		
		/**
		 * Using the <tt>from</tt> text given in parameter, this method can return
		 * an intermediate String between <tt>from</tt> and <tt>to</tt>, using
		 * only the add information stored in the <code>VersionFiddObject</code>.
		 * 
		 * @param from
		 *          a String object
		 * @return a String object
		 */
		public String getAdded(String from) {
			String to = from;
			
			to = to.substring(0, addStart) + addString + to.substring(addStart);
			
			return to;
		}
		
		/**
		 * The shift is the difference between the number of characters to add and
		 * the number of characters that are removed by this
		 * <code>VersionDiffObject</code> object. It helps to adapt the next ones.
		 * 
		 * @return an integer, 0 if there is no shift.
		 */
		public int getShift() {
			return addString.length() - (delEnd - delStart + 1);
		}
		
		/**
		 * Shifts the current object with the given value.
		 * 
		 * @param shift an integer
		 */
		public void shift(int shift) {
			delStart += shift;
			if (delEnd >= 0)
				delEnd += shift;
		}
	}
}